#include <Eratostenes.hpp>
#include <EratostenesOpenMP.hpp>
#include <PalindromeChecker.hpp>
#include <WordDataReader.hpp>
#include <PhraseDataReader.hpp>
#include <iostream>
#include <omp.h>
#include <ctime>

// All prime numbers until this value will be calculated
const int maxPrimeNumber = 1000000;

// Qty of threads in Eratostenes's calculation
const int maxNumThreadsEratostenes = 10;

// Qty of threads in Palindrome's verification
const int maxNumThreadsPalindrome = 10;

int main (int argc, char *argv[]) {
	if(argc != 3){
		std::cout << argv[0] << " TIPO_ENTRADA ENTRADA.TXT\n"
					"Tipos possiveis:\n"
					"0: texto curto\n"
					"1: texto longo\n";
		return 1;
	}

	// Vector that storage number of instructions in each algorithm
	long long *icountEra = new long long [maxNumThreadsEratostenes];
	long long *icountPalin = new long long [maxNumThreadsPalindrome];

	for(int i = 0; i < maxNumThreadsEratostenes; i++) icountEra[i] = 0L;
	for(int i = 0; i < maxNumThreadsPalindrome; i++) icountPalin[i] = 0L;

	std::cout << "Generating primes until " << maxPrimeNumber 
	<< std::endl;


	// Variables used to calculate the time elapsed in each algorithm
	double timeEratostenes, timePalindrome;

	Eratostenes * sieve = new EratostenesOpenMP(maxPrimeNumber);

	omp_set_num_threads(maxNumThreadsEratostenes);

	timeEratostenes = omp_get_wtime();
	// Runs the Eratostenes's algorithm
	sieve->calculate(icountEra, maxNumThreadsEratostenes);
	timeEratostenes = omp_get_wtime() - timeEratostenes;

	PalindromeChecker * pC  = new PalindromeChecker();

	std::cout << "Reading input file. Wait.\n";

	DataReader * dR;

	// Creates the reader based on passed argument
	if(argv[1][0] == '0')
		dR = new PhraseDataReader(argv[2]);
	else
		dR = new WordDataReader(argv[2]);


	std::vector<std::pair<std::string, unsigned> >* v;
	
	v = dR->readData();

	int * palcheck;
	bool * pricheck;
	int size = v->size();
	palcheck = new int[size];
	pricheck = new bool[size];
	int sum;

	std::cout << "Input file read successfuly\n";
	std::cout << "Veryfing the text\n";


	omp_set_num_threads(maxNumThreadsEratostenes);
	
	timePalindrome = omp_get_wtime();

	// Verifies if the words are palindrome
	// The indices are splited among the threads 
	// through intercaled division (omp parallel for)

	#pragma omp parallel for private(sum)
	for (int i = 0; i < size; i++){
		if (palcheck[i] = sum = pC->check((*v)[i].first), &icountPalin[omp_get_thread_num()])
			// palcheck[i] contains the sum of the ASCII codes
			pricheck[i] = (*sieve)[sum], icountPalin[omp_get_thread_num()]++; 
			// checking if palcheck[i] is prime is just a verification in an array
		icountPalin[omp_get_thread_num()] += 3L;
	}

	timePalindrome = omp_get_wtime() - timePalindrome;


	std::cout << "\nResults\n";

	// Prints the results
	for (int i = 0; i < size; i++)
		if (sum = palcheck[i])
			std::cout <<
			"(" << (*v)[i].second << "x) " <<
			(*v)[i].first << " : sum " << sum << " is" <<
			(pricheck[i]?" ":" NOT ") << "prime.\n";


	std::cout << std::endl;

	// Prints the countes
	for(int i = 0; i< maxNumThreadsEratostenes; i++)
		std::cout << "Eratostenes's [" << i << "] instruction count: " << icountEra[i] << "\n";

	std::cout << std::endl;

	for(int i = 0; i< maxNumThreadsPalindrome; i++)
		std::cout << "Palindrome check's [" << i << "] instruction count: " << icountPalin[i] << "\n";

	std::cout << std::endl;

	std::cout << "Eratostenes's required time: " << timeEratostenes << "s\n";
	std::cout << "Palindrome check's required time: " << timePalindrome << "s\n";

	delete [] icountEra;
	delete [] icountPalin;
	delete [] palcheck;
	delete [] pricheck;
	delete v;
	delete pC;
	delete sieve;
	return 0;
}
